<template>
    <el-dialog title="菜单授权" v-model="dialogVisible" :before-close="handleClose" :close-on-click-modal="false" top="10vh"
        width="20%">
        <div>
            <el-tree v-loading="treeLoading" :expand-on-click-node="false" :indent="24" :filter-node-method="filterNode"
                default-expand-all class="filter-tree" :data="treeMenus" :node-key="nodeKey" :props="defaultProps"
                ref="treeRef" highlight-current show-checkbox @check="handleCheckNode" check-strictly>
                <template #default="{ node }">
                    <div class="splitpane-tree-node">
                        <span :title="node.label">{{ node.label }}</span>
                    </div>
                </template>
            </el-tree>
        </div>
        <span slot="footer" class="dialog-footer">
            <el-button @click="handleClose()">关闭</el-button>
            <el-button type="primary" :loading="btnLoading" @click="submitClick()">确 定</el-button>
        </span>
    </el-dialog>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { getMenuList } from "@/api/sys/menuApi.ts";
import { getRoleByMenus, roleAuthorizeMenus } from "@/api/sys/roleApi.ts";

import { ElMessage } from 'element-plus';

interface Tree {
    [key: string]: any
}
const treeLoading = ref(false)
const treeRef = ref();
const defaultProps = {
    children: 'children',
    label: 'name',
}
const roleId = ref('')
const nodeKey = 'id'

const treeMenus = ref<SysMenuEntity[]>([]);

const btnLoading = ref(false);
const dialogVisible = ref(false);

const filterNode = (value: string, data: Tree) => {
    if (!value) return true
    return data.label.includes(value)
}
const openLoad = (id: string) => {
    dialogVisible.value = true;
    roleId.value = id;
    init()
}
const init = async () => {
    await initMenusTree();
    await initSelectMenusTree();
}
const initMenusTree = async () => {
    const res = await getMenuList({});
    if (res.code == 200) {
        treeMenus.value = res.result;
    }
}



const initSelectMenusTree = async () => {
    const params = {
        roleId: roleId.value
    } as any;
    const res: apiServerResponse = await getRoleByMenus(params);
    if (res.code == 200) {
        const selectMenus = res.result;
        selectMenus.forEach((item: any) => {
            treeRef.value.setChecked(item, true, false);
        })

    }
}
// 将被选中节点的所有父节点自动设置为选中状态
// 同时当取消选中时，关联下级各层子节点也全部取消选中
const handleCheckNode = (curPerNode: SysMenuEntity, status: any) => {
    // 获取被选择节点ID数组
    let selectedNodeKeyArr = status.checkedKeys;
    // 初始化被选择节点的各层级父节点ID集合
    // 这里要使用set集合，而不能用数组，因为权限树分为三层，会出现二层和三层节点拥有相同顶层节点的情况，需要去重
    let selectedNodeParentKeySet = new Set;
    // 遍历被选择节点ID数组，寻找每个节点的各层级父节点
    selectedNodeKeyArr.forEach((nodeKey: any) => {
        // 获取当前节点的ID
        let curNodeKey = nodeKey;
        // 循环遍历当前节点各层级父节点，直到顶层节点为止，顶层节点的父节点ID为0
        while (0 != curNodeKey) {
            // 根据当前节点ID，获得当前节点对象
            let curNode = treeRef.value.getNode(curNodeKey);
            // 从当前节点对象中获取父节点ID
            // 不能使用node.parent.data.id方法获取父节点id，原因是当遍历到顶层节点时，parent方法会报错，因为顶层节点没有父节点
            let parentID = curNode.data.pid;
            // console.info(Object.keys(curNode.data));
            // 判断当前节点的父节点ID是否为0，若为零说明已经遍历到顶层节点，不需要将ID添加到父节点ID集合
            // if (0 != parentID) {
            selectedNodeParentKeySet.add(parentID);
            // }
            // 将当前节点的父节点ID赋值给当前节点ID参数，以便下一次循环遍历时作为获取目标节点对象的参数
            curNodeKey = parentID;
        }
    })

    // 将被选中节点的各层级父节点也置于选中状态
    selectedNodeParentKeySet.forEach((parentKey) => {
        treeRef.value.setChecked(parentKey, true, false);
    })

    // 初始化取消选中节点id数组，该数组记录的是被取消选中节点以及下级各层子节点的ID
    let unSelectedNodeKeyArr = [];
    // 根据当前节点id是否包含在被选择节点id集合中判断，当前被操作节点是被选中，还是被取消选中
    // 当取消选择时才进行子节点状态变更
    if (!selectedNodeKeyArr.includes(curPerNode.id)) {
        // 将当前取消选中的节点对象id增加到取消选中节点数组中
        unSelectedNodeKeyArr.push(curPerNode.id);
        // 初始化节点类型参数，0代表目录，1代表菜单，2代表按钮
        let perType = '0';
        // 循环判断当前节点类型参数是否为m，若是则停止循环
        // 之所以停止条件设置为菜单，是因为当以菜单节点作为父节点进行判断时，符合要求的节点全部都是叶子结点，不需要再进行一轮叶子结点的判断，因为叶子结点不会再有子节点
        while ("1" != perType) {
            // 初始化取消选中节点id数组长度
            let length = unSelectedNodeKeyArr.length;
            // 初始化被取消选中的节点对象
            let unSelectedNode = ref([]);
            // 遍历取消选中节点ID数组
            unSelectedNodeKeyArr.forEach((unSelectedNodeKey) => {
                // 根据被取消的选中节点的ID获得对应的节点对象
                unSelectedNode = treeRef.value.getNode(unSelectedNodeKey);
                // 遍历被选中节点id数组
                selectedNodeKeyArr.forEach((nodeKey: any) => {
                    // 根据被选中节点的ID获得对应的节点对象
                    let node = treeRef.value.getNode(nodeKey);
                    // 获得节点对象的父节点ID，permissionParentId是后台orm对象属性
                    let parentID = node.data.pid;
                    // 比较被选中节点的父节点ID是否与被取消选中节点ID相同，如果相同，说明当前被选中节点的父节点已被取消选中，则当前节点也应被取消选中
                    if (parentID == unSelectedNodeKey) {
                        // 将当前节点ID增加到取消选中节点数组中
                        unSelectedNodeKeyArr.push(node.data.id);
                    }
                })
            })
            /*
            根据数组长度变化判断是否有元素被添加到取消选中节点数组中。
            如果没有，说明被取消节点没有子节点，或者其子节点都处于未被选中状态，循环可以结束；
            反之，则说明被取消节点作为父节点，还有被选中的子节点存在，则随便找一个子节点记录它的类型即可。
            permissionType是后台orm对象属性。
            */
            perType = length == unSelectedNodeKeyArr.length ? '1' : (unSelectedNode.data.menuType);
        }

        // 遍历取消选中节点ID数组，将数组中还处于选中状态的节点全部取消选中
        // 也就是将被操作的取消选中节点本身，以及还处于选中状态的子节点全部关联取消选中
        unSelectedNodeKeyArr.forEach((nodeKey) => {
            treeRef.value.setChecked(nodeKey, false, false);
        })
    }
}

const submitClick = async () => {

    btnLoading.value = true;
    setTimeout(() => {
        btnLoading.value = false;
    }, 5000);
    try {
        const list = [...treeRef.value.getCheckedKeys(), ...treeRef.value.getHalfCheckedKeys()]
        const params = {
            roleId: roleId.value,
            menuId: list
        } as RoleAuthorizeMenusRequestParams
        const res: apiServerResponse = await roleAuthorizeMenus(params)
        if (res.code == 200 && res.result) {
            ElMessage({
                message: '保存成功',
                type: 'success',
            })
            handleClose();
        } else {
            ElMessage.error("保存失败!");
        }
    } catch (error) {
        ElMessage.error("未知错误，请联系管理员！");
    }
    btnLoading.value = false;
}
const handleClose = () => {
    dialogVisible.value = false;
    btnLoading.value = false;
    roleId.value = '';
}

defineExpose({
    openLoad
})
</script>

<style lang="scss" scoped>
.flex-space {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;

    .el-icon {
        padding-right: 10px;
    }
}
</style>